package com.example.admin.service.impl;

import com.example.admin.exception.BusinessException;
import com.example.admin.model.Permission;
import com.example.admin.model.Role;
import com.example.admin.model.User;
import com.example.admin.payload.request.LoginRequest;
import com.example.admin.payload.request.SignupRequest;
import com.example.admin.payload.response.JwtResponse;
import com.example.admin.repository.RoleRepository;
import com.example.admin.repository.UserRepository;
import com.example.admin.security.UserDetailsImpl;
import com.example.admin.repository.PermissionRepository;
import com.example.admin.security.JwtUtils;
import com.example.admin.service.AuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class AuthServiceImpl implements AuthService {
    private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
    
    @Override
    public void resetPassword(String email, String newPassword) {
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new BusinessException("User not found with email: " + email));
        
        user.setPassword(passwordEncoder.encode(newPassword));
        userRepository.save(user);
    }

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private PasswordEncoder encoder;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private PermissionRepository permissionRepository;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public JwtResponse authenticateUser(LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateToken(authentication);
        String refreshToken = jwtUtils.generateRefreshToken(authentication);

        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        List<String> roles = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());

        logUserAction(userDetails.getUsername(), "Logged in");

        return new JwtResponse(jwt,
                refreshToken,
                userDetails.getId(),
                userDetails.getUsername(),
                userDetails.getEmail(),
                roles);
    }

    @Override
    public void registerUser(SignupRequest signUpRequest) throws BusinessException {
        if (userRepository.existsByUsername(signUpRequest.getUsername())) {
            throw new BusinessException("Error: Username is already taken!");
        }

        if (userRepository.existsByEmail(signUpRequest.getEmail())) {
            throw new BusinessException("Error: Email is already in use!");
        }

        User user = new User(signUpRequest.getUsername(),
                signUpRequest.getEmail(),
                encoder.encode(signUpRequest.getPassword()));

        Set<String> strRoles = signUpRequest.getRoles();
        Set<Role> roles = new HashSet<>();

        if (strRoles == null) {
            Role userRole = roleRepository.findByName("ROLE_USER")
                    .orElseThrow(() -> new BusinessException("Error: Role is not found."));
            roles.add(userRole);
        } else {
            strRoles.forEach(role -> {
                Role foundRole = roleRepository.findByName(role)
                        .orElseThrow(() -> new BusinessException("Error: Role " + role + " is not found."));
                roles.add(foundRole);
            });
        }

        user.setRoles(roles);
        userRepository.save(user);

        logUserAction(signUpRequest.getUsername(), "Registered");
    }

    @Override
    public void logout() {
        SecurityContextHolder.clearContext();
        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        logUserAction(userDetails.getUsername(), "Logged out");
    }

    @Override
    public void assignPermissionsToUser(Long userId, Set<Long> permissionIds) throws BusinessException {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new BusinessException("User not found with id: " + userId));

        Set<Permission> permissions = new HashSet<>(permissionRepository.findAllById(permissionIds));
        if (permissions.size() != permissionIds.size()) {
            throw new BusinessException("One or more permissions not found");
        }

        user.setPermissions(permissions);
        userRepository.save(user);

        logUserAction(user.getUsername(), "Assigned permissions to user");
    }

    @Override
    public void assignPermissionsToRole(Long roleId, Set<Long> permissionIds) throws BusinessException {
        Role role = roleRepository.findById(roleId)
                .orElseThrow(() -> new BusinessException("Role not found with id: " + roleId));

        Set<Permission> permissions = new HashSet<>(permissionRepository.findAllById(permissionIds));
        if (permissions.size() != permissionIds.size()) {
            throw new BusinessException("One or more permissions not found");
        }

        role.setPermissions(permissions);
        roleRepository.save(role);

        logUserAction(role.getName(), "Assigned permissions to role");
    }

    @Override
    public JwtResponse refreshToken(String refreshToken) throws BusinessException {
        if (!jwtUtils.validateJwtToken(refreshToken)) {
            throw new BusinessException("Invalid refresh token");
        }

        String username = jwtUtils.getUserNameFromJwtToken(refreshToken);
        UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(username);

        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtUtils.generateToken(authentication);
        String newRefreshToken = jwtUtils.generateRefreshToken(authentication);

        logUserAction(userDetails.getUsername(), "Refreshed token");

        return new JwtResponse(jwt, newRefreshToken, userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(),
                userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
    }

    @Override
    public void logUserAction(String username, String action) {
        logger.info("User {} performed action: {}", username, action);
    }
}
